package osvscanner

import (
	"slices"
	"sort"
	"strings"

	"github.com/google/osv-scanner/internal/output"
	"github.com/google/osv-scanner/internal/sourceanalysis"
	"github.com/google/osv-scanner/pkg/config"
	"github.com/google/osv-scanner/pkg/grouper"
	"github.com/google/osv-scanner/pkg/models"
	"github.com/google/osv-scanner/pkg/osv"
	"github.com/google/osv-scanner/pkg/reporter"
)

// buildVulnerablityResults takes the responses from the OSV API and the deps.dev API
// and converts this into a VulnerabilityResults. As part is this, it groups
// vulnerability information by source location.
// TODO: This function is getting long, we should refactor it
func buildVulnerabilityResults(
	r reporter.Reporter,
	packages []scannedPackage,
	vulnsResp *osv.HydratedBatchedResponse,
	licensesResp [][]models.License,
	actions ScannerActions,
	configManager *config.ConfigManager,
) models.VulnerabilityResults {
	results := models.VulnerabilityResults{
		Results: []models.PackageSource{},
	}
	groupedBySource := map[models.SourceInfo][]models.PackageVulns{}
	for i, rawPkg := range packages {
		includePackage := actions.ShowAllPackages
		var pkg models.PackageVulns

		if rawPkg.Commit != "" {
			pkg.Package.Commit = rawPkg.Commit
			pkg.Package.Name = rawPkg.Name
		} else if rawPkg.PURL != "" {
			var err error
			pkg.Package, err = models.PURLToPackage(rawPkg.PURL)

			if err != nil {
				r.Errorf("Failed to parse purl: %s, with error: %s", rawPkg.PURL, err)
				continue
			}
		}

		if rawPkg.Version != "" && rawPkg.Ecosystem != "" {
			pkg.Package = models.PackageInfo{
				Name:        rawPkg.Name,
				Version:     rawPkg.Version,
				Ecosystem:   string(rawPkg.Ecosystem),
				ImageOrigin: rawPkg.ImageOrigin,
			}
		}

		pkg.DepGroups = rawPkg.DepGroups

		if len(vulnsResp.Results[i].Vulns) > 0 {
			includePackage = true
			pkg.Vulnerabilities = vulnsResp.Results[i].Vulns
			pkg.Groups = grouper.Group(grouper.ConvertVulnerabilityToIDAliases(pkg.Vulnerabilities))
			for i, group := range pkg.Groups {
				pkg.Groups[i].MaxSeverity = output.MaxSeverity(group, pkg)
			}
		}
		if actions.ScanLicensesSummary || len(actions.ScanLicensesAllowlist) > 0 {
			configToUse := configManager.Get(r, rawPkg.Source.Path)
			if override, entry := configToUse.ShouldOverridePackageVersionLicense(pkg.Package.Name, pkg.Package.Version, pkg.Package.Ecosystem); override {
				overrideLicenses := make([]models.License, len(entry.License.Override))
				for j, license := range entry.License.Override {
					overrideLicenses[j] = models.License(license)
				}
				r.Infof("overriding license for package %s/%s/%s with %s\n", pkg.Package.Ecosystem, pkg.Package.Name, pkg.Package.Version, strings.Join(entry.License.Override, ","))
				licensesResp[i] = overrideLicenses
			}
			if len(actions.ScanLicensesAllowlist) > 0 {
				pkg.Licenses = licensesResp[i]
				for _, license := range pkg.Licenses {
					lowerLicense := strings.ToLower(string(license))
					if !slices.ContainsFunc(actions.ScanLicensesAllowlist, func(l string) bool {
						return strings.ToLower(l) == lowerLicense
					}) {
						pkg.LicenseViolations = append(pkg.LicenseViolations, license)
					}
				}
				if len(pkg.LicenseViolations) > 0 {
					includePackage = true
				}
			}
			if actions.ScanLicensesSummary {
				pkg.Licenses = licensesResp[i]
			}
		}
		if includePackage {
			groupedBySource[rawPkg.Source] = append(groupedBySource[rawPkg.Source], pkg)
		}
	}

	for source, packages := range groupedBySource {
		sourceanalysis.Run(r, source, packages, actions.CallAnalysisStates)
		results.Results = append(results.Results, models.PackageSource{
			Source:   source,
			Packages: packages,
		})
	}

	sort.Slice(results.Results, func(i, j int) bool {
		if results.Results[i].Source.Path == results.Results[j].Source.Path {
			return results.Results[i].Source.Type < results.Results[j].Source.Type
		}

		return results.Results[i].Source.Path < results.Results[j].Source.Path
	})

	if len(actions.ScanLicensesAllowlist) > 0 || actions.ScanLicensesSummary {
		results.ExperimentalAnalysisConfig.Licenses.Summary = actions.ScanLicensesSummary
		allowlist := make([]models.License, len(actions.ScanLicensesAllowlist))
		for i, l := range actions.ScanLicensesAllowlist {
			allowlist[i] = models.License(l)
		}
		results.ExperimentalAnalysisConfig.Licenses.Allowlist = allowlist
	}

	return results
}
